// Note that mutter has a compile-time limit of 36
const MAX_WORKSPACES = 16;
-const WORKSPACE_DRAGGING_SCALE = 0.85;
-
const CONTROLS_POP_IN_FRACTION = 0.8;
const CONTROLS_POP_IN_TIME = 0.1;
-function WorkspacesView(width, height, x, y, workspaces) {
- this._init(width, height, x, y, workspaces);
+function WorkspacesView(width, height, x, y, zoomScale, workspaces) {
+ this._init(width, height, x, y, zoomScale, workspaces);
}
WorkspacesView.prototype = {
- _init: function(width, height, x, y, workspaces) {
+ _init: function(width, height, x, y, zoomScale, workspaces) {
this.actor = new St.Group({ style_class: 'workspaces-view' });
this.actor.set_clip(x, y, width, height);
+ // The actor itself isn't a drop target, so we don't want to pick on its area
+ this.actor.set_size(0, 0);
+
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('style-changed', Lang.bind(this,
this._height = height;
this._x = x;
this._y = y;
+ this._zoomScale = zoomScale;
this._spacing = 0;
this._activeWorkspaceX = 0; // x offset of active ws while dragging
this._activeWorkspaceY = 0; // y offset of active ws while dragging
this._animating = false; // tweening
this._scrolling = false; // swipe-scrolling
this._animatingScroll = false; // programatically updating the adjustment
+ this._zoomOut = false; // zoom to a larger area
this._inDrag = false; // dragging a window
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
global.get_current_time());
},
+ zoomOut: function() {
+ if (this._zoomOut)
+ return;
+
+ this._zoomOut = true;
+ this._computeWorkspacePositions();
+ this._updateWorkspaceActors(true);
+ },
+
+ zoomIn: function() {
+ if (!this._zoomOut)
+ return;
+
+ this._zoomOut = false;
+ this._computeWorkspacePositions();
+ this._updateWorkspaceActors(true);
+ },
+
_handleDragOverNewWorkspace: function(source, dropActor, x, y, time) {
if (source.realWindow)
return DND.DragMotionResult.MOVE_DROP;
// actually change the actors to match
_computeWorkspacePositions: function() {
let active = global.screen.get_active_workspace_index();
-
- let scale = this._width / global.screen_width;
- if (this._inDrag)
- scale *= WORKSPACE_DRAGGING_SCALE;
+ let zoomScale = this._zoomOut ? this._zoomScale : 1;
+ let scale = zoomScale * this._width / global.screen_width;
let _width = this._workspaces[0].actor.width * scale;
let _height = this._workspaces[0].actor.height * scale;
workspace.scale = scale;
workspace.x = this._x + this._activeWorkspaceX;
+
+ // We adjust the center because the zoomScale is to leave space for
+ // the expanded workspace control so we want to zoom to either the
+ // left part of the area or the right part of the area
+ let offset = 0.5 * (1 - this._zoomScale) * this._width;
+ let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
+ if (this._zoomOut)
+ workspace.x += rtl ? offset : - offset;
+
+ // We divide by zoomScale so that adjacent workspaces are always offscreen
+ // except when we are switching between workspaces
workspace.y = this._y + this._activeWorkspaceY
- + (w - active) * (_height + this._spacing);
+ + (w - active) * (_height + this._spacing) / zoomScale;
}
},
return;
this._inDrag = true;
- this._computeWorkspacePositions();
- this._updateWorkspaceActors(true);
this._dragMonitor = {
dragMotion: Lang.bind(this, this._onDragMotion)
}
DND.removeMonitor(this._dragMonitor);
this._inDrag = false;
- this._computeWorkspacePositions();
- this._updateWorkspaceActors(true);
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].setReservedSlot(null);
Signals.addSignalMethods(WorkspacesView.prototype);
-function WorkspaceControlsContainer(controls) {
- this._init(controls);
-}
-
-WorkspaceControlsContainer.prototype = {
- _init: function(controls) {
- this.actor = new Shell.GenericContainer({ clip_to_allocation: true });
- this.actor.connect('get-preferred-width',
- Lang.bind(this, this._getPreferredWidth));
- this.actor.connect('get-preferred-height',
- Lang.bind(this, this._getPreferredHeight));
- this.actor.connect('allocate', Lang.bind(this, this._allocate));
-
- this.actor.add_actor(controls);
-
- this._controls = controls;
- this._controls.reactive = true;
- this._controls.track_hover = true;
- this._controls.connect('notify::hover',
- Lang.bind(this, this._onHoverChanged));
-
- this._itemDragBeginId = 0;
- this._itemDragEndId = 0;
- this._windowDragBeginId = 0;
- this._windowDragEndId = 0;
- },
-
- show: function() {
- if (this._itemDragBeginId == 0)
- this._itemDragBeginId = Main.overview.connect('item-drag-begin',
- Lang.bind(this, this.popOut));
- if (this._itemDragEndId == 0)
- this._itemDragEndId = Main.overview.connect('item-drag-end',
- Lang.bind(this, this.popIn));
- if (this._windowDragBeginId == 0)
- this._windowDragBeginId = Main.overview.connect('window-drag-begin',
- Lang.bind(this, this.popOut));
- if (this._windowDragEndId == 0)
- this._windowDragEndId = Main.overview.connect('window-drag-end',
- Lang.bind(this, this.popIn));
- this._controls.x = this._poppedInX();
- },
-
- hide: function() {
- if (this._itemDragBeginId > 0) {
- Main.overview.disconnect(this._itemDragBeginId);
- this._itemDragBeginId = 0;
- }
- if (this._itemEndBeginId > 0) {
- Main.overview.disconnect(this._itemDragEndId);
- this._itemDragEndId = 0;
- }
- if (this._windowDragBeginId > 0) {
- Main.overview.disconnect(this._windowDragBeginId);
- this._windowDragBeginId = 0;
- }
- if (this._windowDragEndId > 0) {
- Main.overview.disconnect(this._windowDragEndId);
- this._windowDragEndId = 0;
- }
- },
-
- _getPreferredWidth: function(actor, forHeight, alloc) {
- let [minWidth, natWidth] = this._controls.get_preferred_width(-1);
- alloc.min_size = minWidth;
- alloc.natural_size = natWidth;
- },
-
- // Always request the full width ...
- _getPreferredHeight: function(actor, forWidth, alloc) {
- let [minHeight, natHeight] = this._controls.get_preferred_height(-1);
- alloc.min_size = minHeight;
- alloc.natural_size = natHeight;
- },
-
- // ... even when the controls are popped in, to keep the width constant.
- // This is necessary as the workspace size is determined once before
- // entering the overview, when the controls are popped in - if the width
- // varied, the workspaces would be given too much width, and the controls
- // would be overlapped by the workspaces when popped out, rendering them
- // useless.
- _allocate: function(actor, box, flags) {
- let childBox = new Clutter.ActorBox();
- childBox.x1 = this._controls.x;
- childBox.x2 = this._controls.x + this._controls.width;
- childBox.y1 = 0;
- childBox.y2 = box.y2 - box.y1;
- this._controls.allocate(childBox, flags);
- },
-
- _onHoverChanged: function() {
- if (this._controls.hover)
- this.popOut();
- else
- this.popIn();
- },
-
- _poppedInX: function() {
- let x = CONTROLS_POP_IN_FRACTION * this._controls.width;
- if (St.Widget.get_default_direction() == St.TextDirection.RTL)
- return -x;
- return x;
- },
-
- popOut: function() {
- Tweener.addTween(this._controls,
- { x: 0,
- time: CONTROLS_POP_IN_TIME,
- transition: 'easeOutQuad' });
- },
-
- popIn: function() {
- Tweener.addTween(this._controls,
- { x: this._poppedInX(),
- time: CONTROLS_POP_IN_TIME,
- transition: 'easeOutQuad' });
- }
-};
-
function WorkspacesDisplay() {
this._init();
}
WorkspacesDisplay.prototype = {
_init: function() {
- this.actor = new St.BoxLayout();
-
- let workspacesBox = new St.BoxLayout({ vertical: true });
- this.actor.add(workspacesBox, { expand: true });
-
- // placeholder for window previews
- this._workspacesBin = new St.Bin();
- workspacesBox.add(this._workspacesBin, { expand: true });
+ this.actor = new St.Group();
+ this.actor.set_size(0, 0);
let controls = new St.BoxLayout({ vertical: true,
style_class: 'workspace-controls' });
- this._controlsContainer = new WorkspaceControlsContainer(controls);
- this.actor.add(this._controlsContainer.actor);
+ this._controls = controls;
+ this.actor.add_actor(controls);
+
+ controls.reactive = true;
+ controls.track_hover = true;
+ controls.connect('notify::hover',
+ Lang.bind(this, this._onControlsHoverChanged));
// Add/remove workspace buttons
this._removeButton = new St.Button({ label: '–', // n-dash
controls.add(this._addButton, { expand: true });
this.workspacesView = null;
+
+ this._inDrag = false;
+ this._zoomOut = false;
+
this._nWorkspacesNotifyId = 0;
+ this._switchWorkspaceNotifyId = 0;
+
+ this._itemDragBeginId = 0;
+ this._itemDragEndId = 0;
+ this._windowDragBeginId = 0;
+ this._windowDragEndId = 0;
},
show: function() {
- this._controlsContainer.show();
+ this._controls.show();
this._workspaces = [];
for (let i = 0; i < global.screen.n_workspaces; i++) {
this._workspaces[i] = new Workspace.Workspace(metaWorkspace);
}
- let binAllocation = this._workspacesBin.allocation;
- let binWidth = binAllocation.x2 - binAllocation.x1;
- let binHeight = binAllocation.y2 - binAllocation.y1;
+ let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
+
+ let totalAllocation = this.actor.allocation;
+ let totalWidth = totalAllocation.x2 - totalAllocation.x1;
+ // XXXX: 50 is just a hack for message tray compensation
+ let totalHeight = totalAllocation.y2 - totalAllocation.y1 - 50;
+
+ let [controlsMin, controlsNatural] = this._controls.get_preferred_width(-1);
+ let controlsReserved = controlsNatural * (1 - CONTROLS_POP_IN_FRACTION);
+
+ totalWidth -= controlsReserved;
// Workspaces expect to have the same ratio as the screen, so take
- // this into account when fitting the workspace into the bin
+ // this into account when fitting the workspace into the available space
let width, height;
- let binRatio = binWidth / binHeight;
+ let totalRatio = totalWidth / totalHeight;
let wsRatio = global.screen_width / global.screen_height;
- if (wsRatio > binRatio) {
- width = binWidth;
- height = Math.floor(binWidth / wsRatio);
+ if (wsRatio > totalRatio) {
+ width = totalWidth;
+ height = Math.floor(totalWidth / wsRatio);
} else {
- width = Math.floor(binHeight * wsRatio);
- height = binHeight;
+ width = Math.floor(totalHeight * wsRatio);
+ height = totalHeight;
}
- // Position workspaces as if they were parented to this._workspacesBin
- let [x, y] = this._workspacesBin.get_transformed_position();
- x = Math.floor(x + Math.abs(binWidth - width) / 2);
- y = Math.floor(y + Math.abs(binHeight - height) / 2);
+ // Position workspaces in the available space
+ let [x, y] = this.actor.get_transformed_position();
+ x = Math.floor(x + Math.abs(totalWidth - width) / 2);
+ y = Math.floor(y + Math.abs(totalHeight - height) / 2);
+
+ if (rtl)
+ x += controlsReserved;
- let newView = new WorkspacesView(width, height, x, y, this._workspaces);
+ this._controls.x = this._getControlsX();
+ this._controls.height = totalHeight;
+
+ let zoomScale = (totalWidth - controlsNatural) / totalWidth;
+ let newView = new WorkspacesView(width, height, x, y, zoomScale, this._workspaces);
if (this.workspacesView)
this.workspacesView.destroy();
this._nWorkspacesNotifyId =
global.screen.connect('notify::n-workspaces',
Lang.bind(this, this._workspacesChanged));
+
+ if (this._itemDragBeginId == 0)
+ this._itemDragBeginId = Main.overview.connect('item-drag-begin',
+ Lang.bind(this, this._dragBegin));
+ if (this._itemDragEndId == 0)
+ this._itemDragEndId = Main.overview.connect('item-drag-end',
+ Lang.bind(this, this._dragEnd));
+ if (this._windowDragBeginId == 0)
+ this._windowDragBeginId = Main.overview.connect('window-drag-begin',
+ Lang.bind(this, this._dragBegin));
+ if (this._windowDragEndId == 0)
+ this._windowDragEndId = Main.overview.connect('window-drag-end',
+ Lang.bind(this, this._dragEnd));
},
hide: function() {
- this._controlsContainer.hide();
+ this._controls.hide();
- if (this._nWorkspacesNotifyId > 0)
+ if (this._nWorkspacesNotifyId > 0) {
global.screen.disconnect(this._nWorkspacesNotifyId);
+ this._nWorkspacesNotifyId = 0;
+ }
+ if (this._itemDragBeginId > 0) {
+ Main.overview.disconnect(this._itemDragBeginId);
+ this._itemDragBeginId = 0;
+ }
+ if (this._itemEndBeginId > 0) {
+ Main.overview.disconnect(this._itemDragEndId);
+ this._itemDragEndId = 0;
+ }
+ if (this._windowDragBeginId > 0) {
+ Main.overview.disconnect(this._windowDragBeginId);
+ this._windowDragBeginId = 0;
+ }
+ if (this._windowDragEndId > 0) {
+ Main.overview.disconnect(this._windowDragEndId);
+ this._windowDragEndId = 0;
+ }
+
this.workspacesView.destroy();
this.workspacesView = null;
for (let w = 0; w < this._workspaces.length; w++) {
this.workspacesView.updateWorkspaces(oldNumWorkspaces,
newNumWorkspaces,
lostWorkspaces);
+ },
+
+ _getControlsX: function() {
+ let totalAllocation = this.actor.allocation;
+ let totalWidth = totalAllocation.x2 - totalAllocation.x1;
+ let [controlsMin, controlsNatural] = this._controls.get_preferred_width(-1);
+ let controlsReserved = controlsNatural * (1 - CONTROLS_POP_IN_FRACTION);
+
+ let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
+ let width = this._zoomOut ? controlsNatural : controlsReserved;
+ if (rtl)
+ return width;
+ else
+ return totalWidth - width;
+ },
+
+ _updateZoom : function() {
+ if (Main.overview.animationInProgress)
+ return;
+
+ let shouldZoom = this._controls.hover || this._inDrag;
+ if (shouldZoom != this._zoomOut) {
+ this._zoomOut = shouldZoom;
+
+ if (!this.workspacesView)
+ return;
+
+ Tweener.addTween(this._controls,
+ { x: this._getControlsX(),
+ time: WORKSPACE_SWITCH_TIME,
+ transition: 'easeOutQuad' });
+
+ if (shouldZoom)
+ this.workspacesView.zoomOut();
+ else
+ this.workspacesView.zoomIn();
+ }
+ },
+
+ _onControlsHoverChanged: function() {
+ this._updateZoom();
+ },
+
+ _dragBegin: function() {
+ this._inDrag = true;
+ this._updateZoom();
+ },
+
+ _dragEnd: function() {
+ this._inDrag = false;
+ this._updateZoom();
}
};
Signals.addSignalMethods(WorkspacesDisplay.prototype);